/*
 *  YATUN  - Yet another tunneling application
 *
 *  Michal Demin - 2007
 *
 *
 *  Tunnel.cpp
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/select.h>

#include <sys/types.h>
#include <unistd.h>


#include "YatunTunnel.h"

//#undef DEBUG

YatunTunnel::YatunTunnel(c_Plug_IO *IOLocal, c_Plug_IO *IORemote) 
{
    this->IOLocal = IOLocal;
    this->IORemote = IORemote;
    IfaceName = NULL;
    Init();
}

YatunTunnel::~YatunTunnel()
{
    Status = STOP;
    sQueue->FlushQueue();
    delete IfaceName;
    delete IORemote;
    delete IOLocal;
}

void YatunTunnel::Init() 
{
    Status = STOP;
    Chain = NULL;
    sQueue = new SchedQueue;
    Pack = new Packet;
    memset (Pack, 0, sizeof (Packet));
    Reliable = 0;
    YatunTunnelTimeout.tv_sec = 2;
    YatunTunnelTimeout.tv_usec = 0;
}

int YatunTunnel::Run() 
{
  int ret;
  struct timeval sel_timeout;
  int maxfd;
  fd_set rfds;
  
  struct timeval LastPacket;
  int Keepalive_sent = 0;
  int interface_up = 0;

  Status = RUN;
  
  sel_timeout.tv_sec = 0;
  sel_timeout.tv_usec = 2000;
   
  State = CONNECTED;
  
  do {   

    if ((State == CONNECTED) && !interface_up) { 
       interface_up = 1; 
       if (ScriptUP) { 
                fprintf(stderr, "Executing '%s' ... \n", ScriptUP); 
                execlp(ScriptUP, ScriptUP, IfaceName, (char *)0);
      } 
    } 

 
    if ((State == DISCONNECTED) && interface_up) { 
      interface_up = 0; 
      if (ScriptDOWN) { 
                fprintf(stderr, "Executing '%s' ... \n", ScriptDOWN); 
		execlp(ScriptDOWN, ScriptDOWN, IfaceName, (char *)0);
      } 
    } 
					  
    FD_ZERO(&rfds);
    FD_SET(IOLocal->GetFD(), &rfds);
    FD_SET(IORemote->GetFD(), &rfds);
    maxfd = MAX (IORemote->GetFD (), IOLocal->GetFD ()) + 1;

    ret = select (maxfd, &rfds, NULL, NULL, &sel_timeout);
    if (ret == -1) {
      printf ("Select error !! \n");
      exit (-1);

    } else if (ret != 0) {
	if (FD_ISSET (IOLocal->GetFD (), &rfds)) {
      	    if (Chain && !Chain->IsReady(YATUN_FORWARD)) {
      		fprintf(stderr, "Processing chain is NOT ready ... \n");
      	    } else if (Reliable && sQueue->IsFull()) {
		fprintf(stderr, "Queue is full ... !!! \n");
	    } else if (IOLocal->Recieve (Pack) > 0) {
		if (Pack->Flag & YATUN_PACKET_DATA) {
	  	    if(Chain) {
	    		Chain->Process(Pack,YATUN_FORWARD);
	  	    }
	    	    if (Reliable) {
			sQueue->AddSend (Pack);
	    	    } else {
	    	        ret = IORemote->Send (Pack);
	    		//fprintf(stderr,"sent packet len = %d \n", ret );
	    	    }
		}
	    }
	}	// if(FD_IsSET(IOLocal)

	if (FD_ISSET (IORemote->GetFD (), &rfds)) {
	    if (Chain && !Chain->IsReady(YATUN_BACKWARD)) {
		fprintf(stderr,"Processing chain is NOT ready ... \n");
	    } else if (IORemote->Recieve (Pack) > 0) {
		gettimeofday (&LastPacket, NULL);
		// printf("IO2->IO1 : %d\n", ret);
		if (Pack->Flag & YATUN_PACKET_DATA) {
		    if(Chain) {
			Chain->Process(Pack,YATUN_BACKWARD);
		    }
		    IOLocal->Send (Pack);
		    
		    if (Reliable) {
			fprintf(stderr, "Sending ACK for seq = %d \n",Pack->SeqNum);
	        	Pack->Lenght = 0;
	        	Pack->Flag = YATUN_PACKET_ACK;	// a posleme ack, ze mame packet
	        	IORemote->Send (Pack);	// ack sa neposiela cez Scheduler
	    	    }
	        } else if (Pack->Flag & YATUN_PACKET_KEEPALIVE) {
	    	    Pack->Flag = YATUN_PACKET_KEEPREPLY;
	    	    Pack->Lenght = 0; 
	    	    IORemote->Send (Pack);
	    	    State = CONNECTED;
	    	    //fprintf (stderr, "Sent KEEPALIVE reply !!! \n");
		} else if (Pack->Flag & YATUN_PACKET_KEEPREPLY) {
	  	    Keepalive_sent = 0;	// vypneme semafor
	  	
		    State = CONNECTED;
	  	    //fprintf(stderr,"recieved reply for KEEPALIVE\n");
		} else if (Pack->Flag & YATUN_PACKET_ACK) {
	    	    if (Reliable) {
			fprintf(stderr, "recieved ack for seq = %d \n", Pack->SeqNum);
	    		sQueue->Remove (Pack->SeqNum);	// dostali sme ack ze packet dosiel
	    	    }
	        }
	    } //if (IORemote->Recieve (Pack) > 0) 
	}	// if(FD_ISSET(IORemote)
    }// if (ret != 0)
    
    /* skontrolujeme timeout  */
    if (State == CONNECTED) {
	struct timeval curTime;
	curTime.tv_sec = 0; // potesime valgrind 
	curTime.tv_usec = 0; 
	gettimeofday (&curTime, NULL);
	timersub (&curTime, &LastPacket, &curTime);
	
	if (timercmp (&curTime, &YatunTunnelTimeout, >)) {
      	//fprintf(stderr, "timeout handler ... \n");
	    if (Keepalive_sent == 0) {
	    // posledna sanca sa zachranit ... natiahneme znova budik, a ak neprijde odpoved tak sa odpojime
		Keepalive_sent = 1;
		gettimeofday (&LastPacket, NULL);
		Pack->Lenght = 0;
		Pack->Flag = YATUN_PACKET_KEEPALIVE;
		fprintf(stderr,"KEEPalive sent ... \n");
		IORemote->Send (Pack);
	    } else {
		fprintf(stderr,"KEEPalive timeout  disconnected ... \n");
		Keepalive_sent = 0;
		State = DISCONNECTED;
	    }
	}
    }


    if (Reliable && State == CONNECTED) {
	sQueue->Check ();		// skontroluj packety
    }

    if (Reliable && State == DISCONNECTED) {
	sQueue->FlushQueue ();
    }

    usleep (1);
  } while (Status!= QUIT && State != DISCONNECTED);

  exit(-1);
}
	

void YatunTunnel::SetEventHandler()
{

}

void YatunTunnel::SetReliable(int inVal) 
{
    Reliable = inVal;
}

int YatunTunnel::GetStatus() 
{
	return Status;
}

void YatunTunnel::SetConf(struct YatunTunnelConf &in)
{
    Reliable = in.Reliable;
    YatunTunnelTimeout = in.YatunTunnelTimeout;
    Chain = in.Chain;
    IfaceName = in.IfaceName;
    ScriptUP = in.ScriptUP;
    ScriptDOWN = in.ScriptDOWN;
// scheduler 
    sQueue->SetConf(in.SchedConf);

}
